Linux之ELF文件初探

  • 对比windowsPE文件与概述

在windows中可执行文件是pe文件格式,Linux中可执行文件是ELF文件,其文件格式是ELF文件格式,在Linux下的ELF文件除了可执行文件(Excutable File),可重定位目标文件(RellocatableObject File)、共享目标文件(SharedObjectFile)、核心转储文件(Core DumpFile)也都是ELF格式文件。

一个典型的ELF文件大致的结构如下

文件头(ELF Header)
程序头表(Program Header Table)
代码段(.text)
数据段(.data)
bss段(.bss)
段表字符串表(.shstrtab)
段表(Section Header Table)
符号表(.symtab)
字符串表(.strtab)
重定位表(.rel.text)
重定位表(.rel.data)
  • 使用Linux下专用工具readelf来查看elf文件信息

  • 查看readelf中的源码

  

FLF文件组成

文件头:

用于记录一个ELF文件的信息(多少位?能够运行的CPU平台是什么?程序的入口点在哪里)

  • 查看ELF头

  

在readelf的源码中变量类型Elf_Internal_Ehdr_,定义在internal头文件中

	#define EI_NIDENT        16                /* Size of e_ident[] */

	typedef struct elf_internal_ehdr {
	  unsigned char                e_ident[EI_NIDENT];   /* ELF "magic number" */
	  bfd_vma                      e_entry;              /* Entry point virtual address */
	  bfd_size_type                e_phoff;              /* Program header table file offset */
	  bfd_size_type                e_shoff;              /* Section header table file offset */
	  unsigned long                e_version;            /* Identifies object file version */
	  unsigned long                e_flags;              /* Processor-specific flags */
	  unsigned short               e_type;               /* Identifies object file type */
	  unsigned short               e_machine;            /* Specifies required architecture */
	  unsigned int                 e_ehsize;             /* ELF header size in bytes */
	  unsigned int                 e_phentsize;          /* Program header table entry size */
	  unsigned int                 e_phnum;              /* Program header table entry count */
	  unsigned int                 e_shentsize;          /* Section header table entry size */
	  unsigned int                 e_shnum;              /* Section header table entry count */
	  unsigned int                 e_shstrndx;           /* Section header string table index */
	} Elf_Internal_Ehdr;
    • 在Linux自带的头文件中查看

   

    #define EI_NIDENT (16)

    typedef struct
    {
      unsigned char     e_ident[EI_NIDENT];        /* Magic number and other info */
      Elf32_Half        e_type;                    /* Object file type */
      Elf32_Half        e_machine;                 /* Architecture */
      Elf32_Word        e_version;                 /* Object file version */
      Elf32_Addr        e_entry;                   /* Entry point virtual address */
      Elf32_Off         e_phoff;                   /* Program header table file offset */
      Elf32_Off         e_shoff;                   /* Section header table file offset */
      Elf32_Word        e_flags;                   /* Processor-specific flags */
      Elf32_Half        e_ehsize;                  /* ELF header size in bytes */
      Elf32_Half        e_phentsize;               /* Program header table entry size */
      Elf32_Half        e_phnum;                   /* Program header table entry count */
      Elf32_Half        e_shentsize;               /* Section header table entry size */
      Elf32_Half        e_shnum;                   /* Section header table entry count */
      Elf32_Half        e_shstrndx;                /* Section header string table index */
    } Elf32_Ehdr;

程序头表:

记录了每个Segment的相关信息,比如类型、对应文件的偏移、大小、属性等,

程序头表和段头表相对独立,它们是由ELF文件头统一管理,程序头表管理ELF文件加载后,ELF文件内可加载段到内存映像的映射关系,一般只有可执行文件中,包含程序头表。程序头表包含多个程序头表项,程序头表描述的对象称为“Segment”,Segment描述的是ELF文件加载后的数据块,段Section描述的是ELF文件加载前的数据块。一般来说,来说两者会存在一定的对应关系,比如代码段.text的加载信息保存在程序头表项对应存放代码的Segment中,数据段.data的加载信息保存在程序头表项对应存放数据的Segment中。有时候为了简化程序头表项的个数,会把同类型的多个段,设置整个ELF文件作为一个Segment

  • 程序头表的数据结构
    /* Program segment header.  */

    typedef struct
    {
      Elf32_Word        p_type;                 /* Segment type */
      Elf32_Off        p_offset;                /* Segment file offset  Segment对应的内容在文件的偏移*/
      Elf32_Addr        p_vaddr;                /* Segment virtual address Segment在内存中的线性地址*/
      Elf32_Addr        p_paddr;                /* Segment physical address */
      Elf32_Word        p_filesz;               /* Segment size in file */
      Elf32_Word        p_memsz;                /* Segment size in memory */
      Elf32_Word        p_flags;                /* Segment flags */
      Elf32_Word        p_align;                /* Segment alignment */
    } Elf32_Phdr;

    #define        PT_NULL           0                /* Program header table entry unused */
    #define        PT_LOAD           1                /* Loadable program segment */
    #define        PT_DYNAMIC        2                /* Dynamic linking information */
    #define        PT_INTERP         3                /* Program interpreter */
    #define        PT_NOTE           4                /* Auxiliary information */
    #define        PT_SHLIB          5                /* Reserved */
    #define        PT_PHDR           6                /* Entry for header table itself */
    #define        PT_TLS            7                /* Thread-local storage segment */
    #define        PT_NUM            8                /* Number of defined types */
    

p_flag权限属性标志

说明
1 可执行 PE_X
2 可写 PE_W
3 可读 PE_R

区段头表:用于记录ELF文件的主要的数据

  • 查看区段

  • 区段头表的数据结构
/* Section header.  */

typedef struct
{
  Elf32_Word        sh_name;            /* Section name (string tbl index) */
  Elf32_Word        sh_type;            /* Section type */
  Elf32_Word        sh_flags;           /* Section flags */
  Elf32_Addr        sh_addr;            /* Section virtual addr at execution */
  Elf32_Off        sh_offset;           /* Section file offset */
  Elf32_Word        sh_size;            /* Section size in bytes */
  Elf32_Word        sh_link;            /* Link to another section */
  Elf32_Word        sh_info;            /* Additional section information */
  Elf32_Word        sh_addralign;       /* Section alignment */
  Elf32_Word        sh_entsize;         /* Entry size if section holds table */
} Elf32_Shdr;

区段头表一共有10个字段,含义如下

(1)sh_name段名,是一个是一个4字节的偏移,记录了段名字符串在段表字符串表(“.shstrtab”段)内的偏移。段表字符串并非表的形式,而是一个文件块,保存了所有的段表字符串内容,存储在“.shstrtab”的段中,根据“.shstrtab”的偏移,加上sh_name便可以访问到每个段对应的段名字符串。

起始地址是000017ac,第一个段表项全0,sh_name在段表项中的偏移是001b,由上图可以得到“.shstrtab”段的偏移是0016ae,所以,计算段名的偏移应该是0x0000001b + 0x000016ae = 0x000016c9

根据计算的结果,查看0x000016c9处:

(2)sh_type,表示段的类型。段的类型有很多,常见的有SHT_PROGBITS,表示程序数据,SHT_SYMTAB表示符号表,SHT_STRTAB表示字符串表,还有专门存放构造函数数组段SHT_INIT_ARRAY,析构函数数组段SHT_FINI_ARRAY。

    •   .txt 代码段
    •   .data 数据段
    •   .radata记录常量数据
    •   .symtab记录符号表(相当于PE文件的导出表)的数据
    •   .strtab 串表段
    •   .shstrtab 有段表 字符串表段
    •   .rel .plt记录某个区段的重定位内容(相当于PE文件的导入表)

对应的宏如下:

 1 /* Legal values for sh_type (section type). */
 2
 3 #define SHT_NULL          0                /* Section header table entry unused */
 4 #define SHT_PROGBITS      1                /* Program data */
 5 #define SHT_SYMTAB        2                /* Symbol table */
 6 #define SHT_STRTAB        3                /* String table */
 7 #define SHT_RELA          4                /* Relocation entries with addends */
 8 #define SHT_HASH          5                /* Symbol hash table */
 9 #define SHT_DYNAMIC       6                /* Dynamic linking information */
10 #define SHT_NOTE          7                /* Notes */
11 #define SHT_NOBITS        8                /* Program space with no data (bss) */
12 #define SHT_REL           9                /* Relocation entries, no addends */
13 #define SHT_SHLIB         10               /* Reserved */
14 #define SHT_DYNSYM        11               /* Dynamic linker symbol table */
15 #define SHT_INIT_ARRAY    14               /* Array of constructors */
16 #define SHT_FINI_ARRAY    15               /* Array of destructors */
17 #define SHT_PREINIT_ARRAY 16               /* Array of pre-constructors */

(3)sh_flags,表示段标志,记录段的属性。其中0表示默认属性,1表示段可写,取值位SHF_WRITE。2表示段加载后需要为之分配内存空间,取值为SHF_ALLOC。4表示可执行,取值为SHF_EXECINSTR,段标志属性可以叠加。

(4)sh_addr,表示段加载后的线性地址

(5)sh_offset,表示段在文件内的偏移,根据此偏移可确定段的位置,读取段的内容。

(6)sh_size,表示段的大小,单位为字节。需要注意的是,如果段类型为SHT_NOBITS,段内没有数据,那么段的大小并非指文件块的大小,而是指段加载后占用内存的大小。

(7~8)sh_link和sh_info表示段的链接信息,一般用于描述符号表段和重定位表段的链接信息。对于符号表段(SHT_SYMTAB),sh_link记录的是符号表使用的串表所在段(一般是,.strtab)对应段表项在段表内的索引。

sh_info 记录的是符号表最后一个局部符号的符号表项在符号表内的索引加1,一般恰好是第一个全局符号的符号表项索引,这样可以帮助连接器更快的地定位到第一个全局符号。如下图:段中符号表段的信息sh_info,刚好是局部符号+1的索引。

对于重定位表表段(段类型是SHT_REL),sh_link记录重定位所作用的符号表段表项早段内的索引,而sh_info记录重定位所作用的段对应的段表项在段表中的索引。

sh_type sh_link sh_info
SHT_DYNAMIC 此表项中条目所用到的字符串表在段表中的索引  
SHT_HASH 此哈希表所适用的符号表的段表索引  
SHT_REL 相关符号表的段表索引 重定位所使用的段的段表索引
SHT_RELA 相关联的字符串表的段表索引 最后一个局部符号的符号表索引值+1
其它 SHN_UNDEF 0

(9)sh_addralign,表示段的对齐方式,对齐规则为 sh_offset % sh_addralign = 0,即段的文件偏移必须是sh_addralign的整数倍,sh_addralign的取值必须是2的整数倍,入1、2、4、8等。

对齐值 对齐方式 说明
0 无对齐要求  
1 无对齐要求  
4 对齐4 满足sh_iffset % 4 = 0
16 对齐16 满足sh_iffset % 16 = 0
32 对齐32 满足sh_iffset % 32 = 0

(10) sh_entsize,一般用于保存注入符号表段,重定位表段时,表示段内保存表的表项大小。例如符号表段“.symtab”内保存的符号表的表项大小为sizeof(Elf32——sym)= 16字节,重定位表段“.rel.plt”内保存的重定位表的表项大小为sizeof(Elf32_rel)= 8字节。

ELF符号表(Symbol Table**)

ELF文件的符号表保存了程序中的符号信息,包括程序中的文件名、函数名、全局变量名等,符号表一般保存在名为“.strtab”的段内,该段对应段表项的类型为SHT_SYMTAB。符号表包含多个符号表项,每个符号表项记录了符号的名称、位置、类型等信息。符号表象的数据结构:

typedef struct
{
  Elf32_Word           st_name;                /* Symbol name (string tbl index) */
  Elf32_Addr           st_value;               /* Symbol value */
  Elf32_Word           st_size;                /* Symbol size */
  unsigned char        st_info;                /* Symbol type and binding */
  unsigned char        st_other;               /* Symbol visibility */
  Elf32_Section        st_shndx;               /* Section index */
} Elf32_Sym;

  

ELF重定位表(Reloc Table)

重定位表常见于可重定位目标文件内,对于静态链接生成的可可执行文件,一般不包括重定位表,动态链接生成的可执行文件暂时不讨论。重定位表一般保存在以名为“.rel”开头的段内,该段对应段表项的类型为SHT_REL,ELF文件需要重定位的段,一般都对应一个重定位表,比如代码段“.txt”的重定位表保持在“.rel.text”内,数据段“.data”的重定位表保持在“.rel.data”内。

重定位表包含多个重定位表项,每个重定位表项记录一条重定位信息,包括重定位的符号、位置、类型等。

/* Relocation table entry without addend (in section of type SHT_REL).  */

typedef struct
{
  Elf32_Addr        r_offset;                /* Address */
  Elf32_Word        r_info;                  /* Relocation type and symbol index */
} Elf32_Rel;

ELF串表(String Table)

ELF文件内的段表和符号表需要记录段名和符号名,这些名称都是字符串。然而,段表项和符号表项都是固定长度的数据结构,无法存储不定长的字符串。因此FLE文件将名称字符串内容集中存放在一个段内,称为串表。这些段表项和符号表项只需记录段名字符串或符号名字符串在对应串表项的位置即可。

虽然虽然存储的字符串表的内容称为串表,但是并非表的形式,而是一个文件区域。

 

原文地址:https://www.cnblogs.com/TJTO/p/11470294.html

时间: 2024-11-13 07:52:34

Linux之ELF文件初探的相关文章

linux 修改 elf 文件的dynamic linker 和 rpath

好久没写了,z最近各种事情纠结....天平座的伤不起... 转到正题,最近遇到了linux 下面不同 glibc gcc 不兼容问题,为了使高版本gcc glibc 不依赖于宿主机的环境,做了一系列的工作,结果还算满意,简单记录一下 glibc 版本的问题解决方案在于把 glibc 库抽离出来和可执行文件一起发布 dynamic linker   比较恶心了,由于它是写死在elf 文件的,为了让他在别的机器上跑起来,需要修改 修改目前主要有两种方法 1 编译的时候 -Wl,-dynamic-li

ELF文件的加载过程(load_elf_binary函数详解)--Linux进程的管理与调度(十三)

日期 内核版本 架构 作者 GitHub CSDN 2016-06-04 Linux-4.6 X86 & arm gatieme LinuxDeviceDrivers Linux进程管理与调度-之-进程的描述 加载和动态链接 从编译/链接和运行的角度看,应用程序和库程序的连接有两种方式. 一种是固定的.静态的连接,就是把需要用到的库函数的目标代码(二进制)代码从程序库中抽取出来,链接进应用软件的目标映像中: 另一种是动态链接,是指库函数的代码并不进入应用软件的目标映像,应用软件在编译/链接阶段并

Linux 可执行文件 ELF结构 及程序加载运行

Linux下ELF文件类型分为以下几种: 1.可重定位文件,例如SimpleSection.o: 2.可执行文件,例如/bin/bash: 3.共享目标文件,例如/lib/libc.so. 在Linux 可重定位文件 ELF结构一文中,我们已经分析了可重定位文件ELF结构.本文分析可执行文件的ELF结构. 首先附上源代码: SectionMapping.c #include <stdlib.h> int main() { while(1) { sleep(1000); } return 0;

linux实践之ELF文件分析

linux实践之ELF文件分析 下面开始elf文件的分析. 我们首先编写一个简单的C代码. 编译链接生成可执行文件. 首先,查看scn15elf.o文件的详细信息. 以16进制形式查看scn15elf.o文件. 查看scn15elf.o中各个段和符号表的信息. 各个段的详细信息如下. 符号表的信息如下: 使用readelf命令查看各个段的详细信息: 段表信息如下: 符号表信息如下: 下面让我们开始分析文件头吧! 由于我的虚拟机是32位的,我下面就主要以32位的系统进行分析,就不比较32位机和64

Linux内核工程导论——进程:ELF文件执行原理(2)

ELF 强符号与弱符号(本小节是转别人的) 我们经常在编程中碰到一种情况叫符号重复定义.多个目标文件中含有相同名字全局符号的定义,那么这些目标文件链接的时候将会出现符号重复定义的错误.比如我们在目标文件A和目标文件B都定义了一个全局整形变量global,并将它们都初始化,那么链接器将A和B进行链接时会报错: 1 b.o:(.data+0x0): multiple definition of `global'2 a.o:(.data+0x0): first defined here 这种符号的定义

Linux动态链接库.so文件的创建与使用

1. 介绍         使用GNU的工具我们如何在Linux下创建自己的程序函数库?一个"程序函数库"简单的说就是一个文件包含了一些编译好的代码和数据,这些编译好的代码和数据可以在事后供其他的程序使用.程序函数库可以使整个程序更加模块化,更容易重新编译,而且更方便升级.  程序函数库可分为3种类型:静态函数库(static libraries).共享函数库(shared libraries).动态加载函数库(dynamically loaded libraries): 1.静态函数

linux的库文件

一.什么是库 本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行.由于windows和linux的本质不同,因此二者库的二进制是不兼容的. Linux操作系统支持的函数库分为静态库和动态库,动态库又称共享库.Linux系统有几个重要的目录存放相应的函数库,如/lib    /usr/lib. 二.静态函数库.动态函数库 A.  这类库的名字一般是libxxx.a:利用静态函数库编译成的文件比较大,因为整个函数库的所有数据都被整合进目标代码中,他的优点就显而易见了,即编译后的执行

实例分析ELF文件动态链接

参考文献: <ELF V1.2> <程序员的自我修养---链接.装载与库>第6章 可执行文件的装载与进程 第7章 动态链接 <Linux GOT与PLT> 开发平台: [[email protected] dynamic_link]# uname -a Linux tanghuimin 2.6.32-358.el6.x86_64 #1 SMP Fri Feb 22 00:31:26 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux 实例讲解

Linux C头文件查找与动态库搜索

一.编译程序时,头文件路径搜索 本文介绍在linux中头文件的搜索路径,也就是说你通过include指定的头文件,linux下的gcc编译器它是怎么找到它的呢.在此之前,先了解一个基本概念. 头文件是一种文本文件,使用文本编辑器将代码编写好之后,以扩展名.h保存就行了.头文件中一般放一些重复使用的代码,例如函数声明.变量声明.常数定义.宏的定义等等.当使用#include语句将头文件引用时,相当于将头文件中所有内容,复制到#include处.#include有两种写法形式,分别是: #inclu