注册 登录
电子工程世界-论坛 返回首页 EEWORLD首页 频道 EE大学堂 下载中心 Datasheet 专题
oxlm_1的个人空间 https://home.eeworld.com.cn/space-uid-1076641.html [收藏] [复制] [分享] [RSS]
日志

《Linux内核深度解析》 ---- ELF文件解析

已有 55 次阅读2024-12-28 13:54 |个人分类:读书笔记

背景

       Linux应用层存在大量的应用启动过程,这个过程一般是通过以下两个函数实现的。

int execve(const char *filename, char *const argv[], char *const envp[]);
int execveat(int dirfd, const char *pathname, char *const argv[], char *const envp[], int flags);

       而这两个函数的入口参数filename和pathname,其实指向的都是ELF格式的文件(.o,.so,和可执行文件)。也就是说,本质上来说,程序的加载运行就是对ELF格式文件的解析,找到程序内部资源信息和函数入口,并依据此信息初始化内存后,在内存相对封闭的环境内进行程序执行,此时若程序跑飞,仅仅需要对此程序占用的内存做销毁处理即可(不考虑函数内部存在动态内存申请释放的情况下),并不会影响整个系统的稳定性。

ELF文件解析

       和wav等文件类型一样,elf文件也由文件头(表明文件类型和文件的核心参数)和内容部分(程序首部(Program header table),节(Section)和节首部(Section header table))组成。其在数据存储分布上存在以下规律:

 

       可以通过readelf -a 来查看elf文件中的所有信息。

ELF头解析

       ELF头长度固定,总共占用32字节(32位系统)或64字节(64位系统),其各段分布如下:

名称

长度(字节)

功能描述

e_ident

4

第一个字节为0x7F,表示可删除的ASCII编码

第二至四字节为ELF(0x45,0x4C,0x46),代表此文件为ELF文件

1

1表示32为的ELF,2代表64位的ELF

1

字节序(大端还是小端对齐等)

1

版本

1

表示应用二进制接口(ABI)的类型

8

暂未使用,填充0

e_type

2

ELF文件类型,值的含义如下:

0表示没有文件类型

1表示可重定位文件(目标文件)

2表示可执行文件

3表示动态库(.so)

4表示核心转存储文件

0xFF00表示用于特定处理器的语义

0xFFFF表示用于特定处理器的语义

e_machine

2

机器类别,区分执行平台,X86,ARM,ARM64等

e_version

4

版本,用于区分不同的ELF变体,目前的规范文件只定义了版本1

e_entry

4/8

程序入口的虚拟地址,0代表这个elf没有关联的入口

e_phoff

4/8

程序(Program)首部表的文件偏移

e_shoff

4/8

节(Section)首部表的文件偏移

e_flags

4

处理器特定的标记

e_ehsize

2

ELF首部的长度,值为arm32为 0x34(52字节),arm64为0x40(64字节)

e_phentsize

2

程序首部表中表项(Segment)的长度,单位是字节

e_phnum

2

程序首部表中表项的数量

e_shensize

2

节首部表中表项的长度,单位是字节

e_shnum

2

节首部表中表项的数量

e_shstrndx

2

节名称字符串表(.shstrtab)在节首部表中的索引,即代表elf文件中的一个section(字符串表),里面存放了section name

       可以通过readelf -h 来查看elf文件中的ELF头信息

程序首部表解析

       程序首部表中存在多个表项,每个表项的长度都为32字节(32位系统)或48字节(64位系统)

名称

长度(字节)

功能描述

p_type

4

段的类型,常见的如下:

1表示可加载段(PT_LOAD),表示可被加载到内存的段,如代码和数据

3表示解释器段(PT_INTERP),指定把可执行文件映射到虚拟地址空间以后必须调用的解释器,解释器负责链接动态库和解析没有解析的符号。解释器通常是动态链接器,即ld共享库,负责把程序以来的动态库映射到虚拟地址空间

p_flags

4

段的标志,常用的3个权限标志是读、写和执行

p_offset

4/8

段在ELF文件中的偏移

p_vaddrp_vaddr

4/8

段映射到内存后的虚拟地址

p_paddr

4/8

段映射到内存后的物理地址

p_filez

4

段在ELF文件中占用的长度

p_memsz

4

段在内存中占用的长度

p_align

4

段的对齐值,p_vaddr和p_paddr对p_align取模后为0

       可以通过readelf -l 来查看elf文件中的程序首部表信息

节首部表解析

       同样的,节首部表也是固定长度的,其占用40字节(32位系统)或48字节(64位系统)。

名称

长度(字节)

功能含义

sh_name

4

所指向的节的名字

sh_type

4

所指向的节的类型

sh_flags

4

所指向的节的属性

sh_addr

4/8

所指向节在执行时的虚拟地址

sh_offset

4/8

所指向的字节在ELF文件中的偏移量

sh_size

4

所指向的字节占用的字节数

sh_link

4

关联节头的下表索引

sh_info

4

附加的节信息

sh_addralign

4

节对齐值

sh_entsize

4

如果节包含一个表项长度固定的表,如符号表,那么这个成员存放表项的长度

       可以通过readelf -S 来查看elf文件中的节首部表信息

重要的节及说明

名称

说明

.text

代码节(也称文本节),通常称代码段,包含程序的机器指令

.data

数据节,也称数据段,包含已初始化的数据,程序在运行器件可以修改

.rodata

只读数据

.bss

没有初始化的数据,在程序开始运行前用0填充

.interp

保存解释器的名称,通常是动态链接库(ld共享库)

.shstrtab

节名称字符串表

.symtab

符号表。符号包括函数和全局变量,符号名称存放在字符串表中,符号表存储符号名称在字符串表里的偏移。可以通过readelf --symbols 查看

.strtab

字符串表,存放符号表所需的字符串

.init

程序初始化时执行的机器指令

.fini

程序结束时执行的机器指令

.dynamic

存放动态链接信息,包含程序依赖的所有动态库,这是动态链接器需要的信息,可以通过readelf --dynamic 查看

.dynsym

存放动态符号表,包含需要动态链接的所有符号,即程序所引用的动态库里面的函数和全局变量,这是动态链接器需要的信息,可以通过readelf --dyn-syms 查看

.dynstr

存放一个字符串表,包含动态链接需要的所有字符串,即动态库的名称、函数名称和全局变量的名称。.dynamic节不直接存储动态库的名称,而是存储库名称在该字符串表里的偏移

.rel.xxxx或.rela.xxxx

用于xxxx节区的重定位信息,记录了需要在链接时修改的指令

部分节内容格式

.symtab

       如果节是符号表类型,其内部存储的数据便遵循以下规则:

名称

长度(字节)

功能含义

st_name

4

符号的名字

st_value

4

符号相对于其所在Section偏移的相对地址

st_size

4

符号所占用的字节数

st_info

1

低四位表示符号的作用范围(bit0:全局或局部,bit1:是否是弱引用),高四位表示符号的类型(变量、函数等)

st_other

1

没有意义

st_shndx

2

该符号的值在哪个Section下存储

知识点确认

简单的测试程序编写

#include <stdio.h>

int main(char argc, char *argv)
{
    int a = 0;
    return a;
}

编写makefile文件

TARGET = test
CC = gcc
CFLAGS = -Wall -g
SRC = test.c
OBJ = $(SRC:.c=.o)

all: $(TARGET)

$(TARGET): $(OBJ)
    $(CC) $(CFLAGS) -o $@ $^

%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

clean:
    rm -f $(OBJ) $(TARGET)

.PHONY: all clean

        通过运行make后生成test文件,

        由于readelf -a命令输出的内容过多,虽然能够较为全面的看清楚elf文件内容,但不便于文档展开分析,因此直接查看具体细节的命令。

查看ELF头

readelf -h test

ELF Header:
Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class:                             ELF64
Data:                              2's complement, little endian
Version:                           1 (current)
OS/ABI:                            UNIX - System V
ABI Version:                       0
Type:                              EXEC (Executable file)
Machine:                           Advanced Micro Devices X86-64
Version:                           0x1
Entry point address:               0x400400
Start of program headers:          64 (bytes into file)
Start of section headers:          5128 (bytes into file)
Flags:                             0x0
Size of this header:               64 (bytes)
Size of program headers:           56 (bytes)
Number of program headers:         9
Size of section headers:           64 (bytes)
Number of section headers:         35
Section header string table index: 32

其对应结构图下

 

        经过对比,会发现其实readelf的作用就是帮助我们把枯燥的数据解码成直观易懂的提示内容。

查看程序首部表

readelf -l test

Elf file type is EXEC (Executable file)
Entry point 0x400400
There are 9 program headers, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  PHDR           0x0000000000000040 0x0000000000400040 0x0000000000400040
                 0x00000000000001f8 0x00000000000001f8  R E    8
  INTERP         0x0000000000000238 0x0000000000400238 0x0000000000400238
                 0x000000000000001c 0x000000000000001c  R      1
      [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x00000000000006bc 0x00000000000006bc  R E    200000
  LOAD           0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                 0x0000000000000228 0x0000000000000230  RW     200000
  DYNAMIC        0x0000000000000e28 0x0000000000600e28 0x0000000000600e28
                 0x00000000000001d0 0x00000000000001d0  RW     8
  NOTE           0x0000000000000254 0x0000000000400254 0x0000000000400254
                 0x0000000000000044 0x0000000000000044  R      4
  GNU_EH_FRAME   0x0000000000000594 0x0000000000400594 0x0000000000400594
                 0x0000000000000034 0x0000000000000034  R      4
  GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
                 0x0000000000000000 0x0000000000000000  RW     10
  GNU_RELRO      0x0000000000000e10 0x0000000000600e10 0x0000000000600e10
                 0x00000000000001f0 0x00000000000001f0  R      1

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .interp 
   02     .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame 
   03     .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss 
   04     .dynamic 
   05     .note.ABI-tag .note.gnu.build-id 
   06     .eh_frame_hdr 
   07     
   08     .init_array .fini_array .jcr .dynamic .got 

        以其中第一个程序首部为例,其对应数据结构如下(具体内部每段含义,可以对照程序首部表格式对照查看):

 

查看节首部表

readelf -S test
There are 35 section headers, starting at offset 0x1408:

Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .interp           PROGBITS         0000000000400238  00000238
       000000000000001c  0000000000000000   A       0     0     1
  [ 2] .note.ABI-tag     NOTE             0000000000400254  00000254
       0000000000000020  0000000000000000   A       0     0     4
  [ 3] .note.gnu.build-i NOTE             0000000000400274  00000274
       0000000000000024  0000000000000000   A       0     0     4
  [ 4] .gnu.hash         GNU_HASH         0000000000400298  00000298
       000000000000001c  0000000000000000   A       5     0     8
  [ 5] .dynsym           DYNSYM           00000000004002b8  000002b8
       0000000000000048  0000000000000018   A       6     1     8
  [ 6] .dynstr           STRTAB           0000000000400300  00000300
       0000000000000038  0000000000000000   A       0     0     1
  [ 7] .gnu.version      VERSYM           0000000000400338  00000338
       0000000000000006  0000000000000002   A       5     0     2
  [ 8] .gnu.version_r    VERNEED          0000000000400340  00000340
       0000000000000020  0000000000000000   A       6     1     8
  [ 9] .rela.dyn         RELA             0000000000400360  00000360
       0000000000000018  0000000000000018   A       5     0     8
  [10] .rela.plt         RELA             0000000000400378  00000378
       0000000000000030  0000000000000018   A       5    12     8
  [11] .init             PROGBITS         00000000004003a8  000003a8
       000000000000001a  0000000000000000  AX       0     0     4
  [12] .plt              PROGBITS         00000000004003d0  000003d0
       0000000000000030  0000000000000010  AX       0     0     16
  [13] .text             PROGBITS         0000000000400400  00000400
       0000000000000182  0000000000000000  AX       0     0     16
  [14] .fini             PROGBITS         0000000000400584  00000584
       0000000000000009  0000000000000000  AX       0     0     4
  [15] .rodata           PROGBITS         0000000000400590  00000590
       0000000000000004  0000000000000004  AM       0     0     4
  [16] .eh_frame_hdr     PROGBITS         0000000000400594  00000594
       0000000000000034  0000000000000000   A       0     0     4
  [17] .eh_frame         PROGBITS         00000000004005c8  000005c8
       00000000000000f4  0000000000000000   A       0     0     8
  [18] .init_array       INIT_ARRAY       0000000000600e10  00000e10
       0000000000000008  0000000000000000  WA       0     0     8
  [19] .fini_array       FINI_ARRAY       0000000000600e18  00000e18
       0000000000000008  0000000000000000  WA       0     0     8
  [20] .jcr              PROGBITS         0000000000600e20  00000e20
       0000000000000008  0000000000000000  WA       0     0     8
  [21] .dynamic          DYNAMIC          0000000000600e28  00000e28
       00000000000001d0  0000000000000010  WA       6     0     8
  [22] .got              PROGBITS         0000000000600ff8  00000ff8
       0000000000000008  0000000000000008  WA       0     0     8
  [23] .got.plt          PROGBITS         0000000000601000  00001000
       0000000000000028  0000000000000008  WA       0     0     8
  [24] .data             PROGBITS         0000000000601028  00001028
       0000000000000010  0000000000000000  WA       0     0     8
  [25] .bss              NOBITS           0000000000601038  00001038
       0000000000000008  0000000000000000  WA       0     0     1
  [26] .comment          PROGBITS         0000000000000000  00001038
       000000000000002b  0000000000000001  MS       0     0     1
  [27] .debug_aranges    PROGBITS         0000000000000000  00001063
       0000000000000030  0000000000000000           0     0     1
  [28] .debug_info       PROGBITS         0000000000000000  00001093
       00000000000000c0  0000000000000000           0     0     1
  [29] .debug_abbrev     PROGBITS         0000000000000000  00001153
       000000000000006b  0000000000000000           0     0     1
  [30] .debug_line       PROGBITS         0000000000000000  000011be
       000000000000003b  0000000000000000           0     0     1
  [31] .debug_str        PROGBITS         0000000000000000  000011f9
       00000000000000c7  0000000000000001  MS       0     0     1
  [32] .shstrtab         STRTAB           0000000000000000  000012c0
       0000000000000148  0000000000000000           0     0     1
  [33] .symtab           SYMTAB           0000000000000000  00001cc8
       0000000000000678  0000000000000018          34    50     8
  [34] .strtab           STRTAB           0000000000000000  00002340
       0000000000000224  0000000000000000           0     0     1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

        以其中第二个节为例(具体内部每段含义,可以对照字节首部表格式对照查看):

 

总结

        至此,elf文件的文件格式基本上可以摸清。由于数据各种地址指来指去,数据长度也各种可变,很不便于直接分析固件,好在有对应的额readelf工具,可以程式化的辅助我们解析elf文件,而不必关心文件内某段内部的具体跳转细节。

本文来自论坛,点击查看完整帖子内容。

评论 (0 个评论)

facelist doodle 涂鸦板

您需要登录后才可以评论 登录 | 注册

热门文章